package jass.patches;
import jass.engine.*;
import jass.generators.*;

/** CombReverb as Moorers reverb. See e.g.
    @book{Steiglitz96,
	title		= {A Digital Signal Processing Primer with
                          Applications to Digital Audio and Computer Music},
	author          =  {Ken Steiglitz},
	publisher	= {Addison-Wesley},
	address		= {New York},
	year		= {1996},
    pages = {290--295}}

    Must have delays greater than the bufferSize.
    Defaults are for 25Khz sampling rate from Steiglitz book.
    BUGS: Does not support removal of source.
    
    @author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class CombReverb extends InOut {
    protected int nchannels = 1;
    protected float srate;
    protected int nCombs = 6;
    protected float[] combDelays = {.05f,.056f,.061f,.068f,.072f,.078f}; // delays in seonds
    protected float allpassDelay = .006f; // delay in seonds
    protected float a = .7f; // allpass parameter
    protected float[] R = {.4897f,.6142f,.5976f,.5893f,.581f,.5644f}; // feedback comb parameters
    protected float[] g = {.24f,.26f,.28f,.29f,.3f,.32f}; // low-pass comb parameters
    protected float dryToWet = .9f; // 1 is dry only
    protected LowPassComb[] lpCombs;
    protected AllPass allPass;
    protected Mixer mixer; // mixes combs
    protected Mixer endMixer; // mixes dry and wet

    /** Create. For derived classes.
        @param bufferSize Buffer size used for real-time rendering.
     */
    public CombReverb(int bufferSize) {
        super(bufferSize);
    }

    /** Create. For derived classes.
        @param bufferSize Buffer size used for real-time rendering.
        @param nchannels 1 for mono
     */
    public CombReverb(int bufferSize,int nchannels) {
        super(bufferSize);
        this.nchannels = nchannels;
    }

    /** Create. For derived classes.
        @param bufferSize Buffer size used for real-time rendering.
        @param srate sampling rate in Hz
        @param nCombs number of comb filters
     */
    public CombReverb(int bufferSize,float srate,int nCombs) {
        super(bufferSize);
        this.srate = srate;
        this.nCombs = nCombs;
        init();
    }
    /** Create. For derived classes.
        @param nchannels 1 for mono
        @param bufferSize Buffer size used for real-time rendering.
        @param srate sampling rate in Hz
        @param nCombs number of comb filters
     */
    public CombReverb(int bufferSize,float srate,int nCombs,int nchannels) {
        super(bufferSize);
        this.nchannels = nchannels;
        this.srate = srate;
        this.nCombs = nCombs;
        init();
    }

    public void setNChannels(int n) {
        nchannels = n;
    }
    
    /** Init and allocate. Defaults are usable.
     */
    protected void init() {
        mixer = new Mixer(bufferSize,nCombs);
        endMixer = new Mixer(bufferSize,2);
        allPass = new AllPass(bufferSize,srate);
        lpCombs = new LowPassComb[nCombs];
        for(int i=0;i<nCombs;i++) {
            lpCombs[i] = new LowPassComb(bufferSize,srate);
        }
        try {
            endMixer.addSource(allPass);
            allPass.addSource(mixer);
            for(int i=0;i<nCombs;i++) {
                mixer.addSource(lpCombs[i]);
                mixer.setGain(i,1f); // constant, not settable
            }
        } catch(SinkIsFullException e) {
            System.out.println(this+" "+e);
        }
        long t = getTime();
        endMixer.setTime(t);
        mixer.setTime(t);
        allPass.setTime(t);
        for(int i=0;i<nCombs;i++) {
            lpCombs[i].setTime(t);
        }
        setAllParameters();
    }

    /**
       Set all filter parameters
     */
    public void setAllParameters() {
        endMixer.setGain(0,1-dryToWet);
        endMixer.setGain(1,dryToWet);
        allPass.setA(a);
        allPass.setM(nchannels*allpassDelay);
        for(int i=0;i<nCombs;i++) {
            lpCombs[i].setL(nchannels*combDelays[i]);
            lpCombs[i].setG(g[i]);
            lpCombs[i].setR(R[i]);
        }
    }

    /** Set feedback of filter k
        @param r feedback
        @param k index of comb filter
     */
    public void setR(float r,int k) {
        R[k] = r;
        lpCombs[k].setR(R[k]);
    }

    /** Set one pole lowpass coefficient g; H(z) = 1/(1-g/z))
        @param g lowpass filter coefficient
        @param k index of comb filter
     */
    public void setG(float g,int k) {
        this.g[k] = g;
        lpCombs[k].setG(this.g[k]);
    }

    /** Set dryToWet ratio
        @param d dry to wet. 1 is dry only
     */
    public void setDryToWet(float d) {
        dryToWet = d;
        endMixer.setGain(0,1-dryToWet);
        endMixer.setGain(1,dryToWet);
    }

    /** Set allpass coeff. a: H(z) = (z^_{m} + a)/(1 + a*z^{-m})
        @param a allpass coeff
     */
    public void setA(float a) {
        this.a = a;
        allPass.setA(a);
    }
    
    /** Set allpass delay: H(z) = (z^_{m} + a)/(1 + a*z^{-m})
        Make sure is not snaller than buffersize
        @param del allpass delay in seconds
     */
    public void setM(float del) {
        allpassDelay = del;
        allPass.setM(del);
    }

    /** Set comb delay. Make sure is not snaller than buffersize
        @param del allpass delay in seconds
        @param k index of comb filter
     */
    public void setL(float del,int k) {
        combDelays[k] = del;
        lpCombs[k].setL(combDelays[k]);
    }
    
    /** Add source to Sink. Override to allow one input only and add to mixer with
        gain coefficient a and to delay 2.
        This will be called after init() so mixer will already have 2 inputs
        @param s Source to add.
        @return object representing Source in Sink (may be null).
    */
    public Object addSource(Source s) throws SinkIsFullException {
        if(getSources().length > 0) {
            throw new SinkIsFullException();
        } else {
            endMixer.addSource(s);
            for(int i=0;i<nCombs;i++) {
                lpCombs[i].addSource(s);
            }
            // add to the superclass. THis is for administrative reasons only,
            // the source cache is not used here.
            return super.addSource(s);
        }
    }

    /** Compute the next buffer and store in member float[] buf.
     */
    protected void computeBuffer() {
        // Time has now been incremented and external Sources
        // have been called, the result of which is in the cache.
        // But we won't use this and just call getBuffer on the
        // contained subpatch. It will call getBuffer() on
        // atached Sources but they are already at this time so will
        // just return their cached buffer. We are wasting only an extra method call.
        try {
            buf = endMixer.getBuffer(getTime());
        } catch(BufferNotAvailableException e) {
            System.out.println(this+" "+e);            
        }
    }

}
